package com.cxz.spring.v2.servlet;

import com.cxz.spring.v2.annotation.*;
import sun.awt.windows.WPrinterJob;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @author cxz
 * @version V1.0
 * @Date 2022/9/7 17:17
 * @desc
 */
public class CxzDispatcherServlet extends HttpServlet {
    // ioc容器
    private Map<String, Object> iocMap = new HashMap<>();

    // 保存application.properties 配置文件中的内容
    private Properties contextConfig = new Properties();

    // 保存所有扫描到的类名
    private List<String> classNames = new ArrayList<>();

    // 保存url 和 method直接的映射关系
    private Map<String, Method> handlerMaping = new HashMap<>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            resp.setContentType("text/html;charset=utf-8");
            this.myDoDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
            resp.getWriter().write("发生了500异常，异常原因：" + e.getMessage());
        }
    }

    /**
     * 转发处理器
     * @param req
     * @param resp
     * @throws Exception
     */
    private void myDoDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        // 获取url
        String uri = req.getRequestURI();
        String contextPath = req.getContextPath();

        // 获取相对路径
        String url = uri.replace(contextPath, "").replace("/+", "/");
        url = Objects.equals("", url) ? "/" : url;

        if(!this.handlerMaping.containsKey(url)) {
            resp.getWriter().write("请求资源不存在，抛404");
        }

        // 将存放到map中的对象，还原为method类型
        Method method = this.handlerMaping.get(url);

        // 获取请求参数
        Map<String, String[]> params = req.getParameterMap();

        // 调用匹配到的method方法
        Object o = this.iocMap.get(method.getDeclaringClass().getSimpleName());

        if(params.containsKey("name")) {
            String name = params.get("name")[0];
            method.invoke(o, new Object[]{req, resp, name});
        } else {
            method.invoke(o, new Object[]{req, resp});
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        // 加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        // 扫描相关类
        doScanner(contextConfig.getProperty("scanPackage"));

        // 实例化controller 和 service 存放到ICO容器
        doIocInstance();

        // 完成依赖注入
        doAutowired();

        // 将url和method对应关系添加到map中
        doHandlerMapping();

        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>> spring v2 服务初始化完成 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
    }


    /**
     * 加载配置文件，将application.properties配置加载到内存对象中
     * @param confPath
     */
    private void doLoadConfig(String confPath) {
        InputStream fis = null;
        try {
            // 读取application.properties文件流
            fis = this.getClass().getClassLoader().getResourceAsStream(confPath);

            // 根据文件流，加载Properties对象
            contextConfig.load(fis);
        } catch (Exception e) {

        } finally {
            if(Objects.nonNull(fis)) {
                try {
                    fis.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 读取扫描包，将包中类名添加到内存list中
     * @param scanPackage
     */
    private void doScanner(String scanPackage) {
        // 配置中的扫描配置是：scanPackage=com.cxz.spring，需要根据该配置，找到目录下所有.class文件，然后将其名称和包路径组成类名
        String scanPackagePath = scanPackage.replaceAll("\\.", "/");
        URL packageUrl = this.getClass().getClassLoader().getResource(scanPackagePath);
        File packageDir = new File(packageUrl.getFile());

        // 遍历文件夹中所有文件，如果文件是.class，则添加到list，如果文件是目录则递归调用，否则跳过处理
        for(File file : packageDir.listFiles()) {
            if(file.isDirectory()) {
                doScanner(scanPackage + "." + file.getName());
                continue;
            }
            if(file.getName().endsWith(".class")) {
                // 获取类名称，类名 = 包名 + .class文件名
                String className = scanPackage + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 做实例化类，将controller和service实例化放入ioc容器中
     */
    private void doIocInstance() {
        // 如果扫描的class列表为空，则直接跳过
        if(classNames.isEmpty()) {
            return;
        }

        try {
            for(String className : classNames) {
                // 利用反射机制，获取class对象
                Class<?> clazz = Class.forName(className);

                // 当类上面有@Controller、@Service时，将其添加到ioc容器中
                if(clazz.isAnnotationPresent(CxzController.class)) {
                    // 直接实例化controller对象
                    Object instance = clazz.newInstance();
                    String beanName = clazz.getSimpleName();
                    iocMap.put(beanName, instance);
                } else if(clazz.isAnnotationPresent(CxzService.class)) {
                    // 实例化service对象
                    Object instance = clazz.newInstance();

                    // 获取注解中定义的bean名称
                    CxzService annotation = clazz.getAnnotation(CxzService.class);
                    String beanName = Objects.equals("", annotation.value()) ? clazz.getSimpleName() : annotation.value();
                    iocMap.put(beanName, instance);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 自动依赖注入
     */
    private void doAutowired() {
        if(iocMap.isEmpty()) {
            return;
        }

        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        for(Map.Entry<String, Object> entry : entries) {
            // 获取ioc容器中实例的字段，Declared 所有的，特定的 字段，包括 private/protected/default
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for(Field field : fields) {
                if(!field.isAnnotationPresent(CxzAutowired.class)) {
                    continue;
                }
                // 获取CxzAutowired注解
                CxzAutowired autowired = field.getAnnotation(CxzAutowired.class);
                // 如果使用autowired注解时，没有指定bean名称，则使用类名作为key查找
                String beanName = autowired.value().trim();
                beanName = Objects.equals("", beanName) ? field.getType().getSimpleName() : beanName;

                // 利用反射机制，动态给字段赋值
                try {
                    field.setAccessible(true);
                    field.set(entry.getValue(), iocMap.get(beanName));
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 添加url和处理method映射关系保存
     */
    private void doHandlerMapping() {
        if(iocMap.isEmpty()) {
            return;
        }
        Set<Map.Entry<String, Object>> entries = iocMap.entrySet();
        for(Map.Entry<String, Object> entry : entries) {
            Class<?> clazz = entry.getValue().getClass();
            if(!clazz.isAnnotationPresent(CxzController.class)) {
                continue;
            }

            // 获取基本url
            String baseUrl = "";
            if(clazz.isAnnotationPresent(CxzRequestMapping.class)) {
                CxzRequestMapping requestMapping = clazz.getAnnotation(CxzRequestMapping.class);
                baseUrl = requestMapping.value();
            }

            // 获取所有的public方法
            for (Method method : clazz.getMethods()) {
                if(!method.isAnnotationPresent(CxzRequestMapping.class)) {
                    continue;
                }
                CxzRequestMapping methodRequestAnno = method.getAnnotation(CxzRequestMapping.class);
                String url = ("/" + baseUrl + "/" + methodRequestAnno.value()).replaceAll("/+", "/");
                handlerMaping.put(url, method);
            }
        }
    }

}
